源码跟踪
通过grep可以查看一些到对应的代码在哪
1 | giantbranch@ubuntu:~$ grep "struct _IO_FILE " -r ./glibc-2.23/ |
至于源码可以通过命令下载
1 | apt-get source libc6-dev |
源码:
1 | struct _IO_FILE { |
其中多个_IO_FILE以struct _IO_FILE *_chain;相连组成链表,头指针是_IO_list_all
可以看到链表的指向是_IO_list_all到2 1 0
1 | gdb-peda$ p _IO_list_all |
更进一步,有一个_IO_FILE_plus,其实他就多一个jump table
1 | struct _IO_FILE_plus |
因为这个file就是_IO_FILE
1 | struct _IO_FILE; |
这个table就是函数指针
1 | struct _IO_jump_t |
那么我们就有可能通过vtable劫持控制流
vtable 劫持分为两种,一种是直接改写 vtable 中的函数指针,通过任意地址写就可以实现。另一种是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针。
例子
https://github.com/ctf-wiki/ctf-challenges/blob/master/pwn/io-file/2018_hctf_the_end/the_end
只有5个写一字节的机会,那我们只能通过exit劫持控制流
1 | void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) |
exit会调用_IO_unbuffer_all,里面会调用setbuf
我们可以对setbuf下断点,即_IO_new_file_setbuf
当然里面也会调用_IO_flush_all_lockp,你去覆盖overflow也是可以的
不行的话对所有jumptable的函数都下个断点,那就知道会调用哪个了
1 | gdb-peda$ p _IO_2_1_stdout_ |
下断点后,断下来,可以看到exit调用了它
1 | gdb-peda$ bt |
那个伪造的vtable地址+0x58的位置必须跟one_gadget的高5位都是一致的
由于本地的libc没有一个onegadget满足条件,所以没成功,但是是成功起shell了
1 | gdb-peda$ c |
exp
1 | #!/usr/bin/env python |
reference
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/